package com.ocean.web.util;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * 单个时间网格内的空间插值工具
 * 功能：固定某一时间切片，在该时间的经纬度网格中对目标经纬度做空间插值，打印关键信息
 */
public class SingleTimeGridInterpolator4 {
    // 可配置参数（通用化无效值和合理范围）
    private final List<Float> invalidValues; // 无效值列表（如[-9999f, -999f]）
    private final float minReasonable;       // 数据合理下限
    private final float maxReasonable;       // 数据合理上限
    private final DecimalFormat dataFormat;  // 数值格式化

    // 空间点数据封装（经纬度+值+有效性）
    static class SpatialPoint {
        float lat;         // 纬度
        float lon;         // 经度
        float value;       // 数据值
        boolean isValid;   // 是否有效
        String invalidReason; // 无效原因（仅无效点有值）

        SpatialPoint(float lat, float lon, float value, boolean isValid, String invalidReason) {
            this.lat = lat;
            this.lon = lon;
            this.value = value;
            this.isValid = isValid;
            this.invalidReason = invalidReason;
        }
    }

    // 插值结果封装（单个时间网格的空间插值信息）
    static class InterpolationResult {
        int targetTimeIndex;       // 目标时间索引（哪个时间切片）
        float targetTimeValue;     // 目标时间的实际值（如小时）
        float targetLat;           // 目标纬度
        float targetLon;           // 目标经度
        float interpolatedValue;   // 空间插值结果
        String strategy;           // 插值策略（如双线性插值）
        List<SpatialPoint> usedValidPoints; // 插值用到的有效空间点（4个邻域中的有效点）
        List<SpatialPoint> usedInvalidPoints; // 插值用到的无效空间点（4个邻域中的无效点）

        InterpolationResult(int timeIndex, float timeValue, float lat, float lon, float result,
                            String strategy, List<SpatialPoint> valid, List<SpatialPoint> invalid) {
            this.targetTimeIndex = timeIndex;
            this.targetTimeValue = timeValue;
            this.targetLat = lat;
            this.targetLon = lon;
            this.interpolatedValue = result;
            this.strategy = strategy;
            this.usedValidPoints = valid;
            this.usedInvalidPoints = invalid;
        }
    }

    /**
     * 构造函数：初始化通用配置
     */
    public SingleTimeGridInterpolator4(List<Float> invalidValues, float minReasonable,
                                      float maxReasonable, String formatPattern) {
        this.invalidValues = invalidValues;
        this.minReasonable = minReasonable;
        this.maxReasonable = maxReasonable;
        this.dataFormat = new DecimalFormat(formatPattern);
    }

    /**
     * 核心方法：单个时间网格内的空间插值
     * @param ncPath NC文件路径
     * @param dataVarName 数据变量名（如"sst"）
     * @param targetTimeIndex 目标时间索引（选择哪个时间切片，如0）
     * @param targetLat 目标纬度
     * @param targetLon 目标经度
     * @return 空间插值结果
     */
    public InterpolationResult interpolateInSingleTimeGrid(String ncPath, String dataVarName,
                                                           int targetTimeIndex, float targetLat, float targetLon) throws IOException {
        NetcdfFile ncFile = null;
        try {
            ncFile = NetcdfFile.open(ncPath);

            // 1. 获取变量
            Variable timeVar = ncFile.findVariable("time");
            Variable latVar = ncFile.findVariable("lat");
            Variable lonVar = ncFile.findVariable("lon");
            Variable dataVar = ncFile.findVariable(dataVarName);

            if (timeVar == null || latVar == null || lonVar == null || dataVar == null) {
                throw new RuntimeException("缺少变量：time/lat/lon/" + dataVarName);
            }

            // 2. 读取基础数据
            float[] times = (float[]) timeVar.read().copyTo1DJavaArray(); // 时间数组（如小时）
            float[] lats = (float[]) latVar.read().copyTo1DJavaArray();   // 纬度数组
            float[] lons = (float[]) lonVar.read().copyTo1DJavaArray();   // 经度数组

            // 校验时间索引是否有效
            if (targetTimeIndex < 0 || targetTimeIndex >= times.length) {
                throw new RuntimeException("目标时间索引无效（范围：0~" + (times.length - 1) + "）");
            }
            float targetTimeValue = times[targetTimeIndex]; // 目标时间的实际值

            // 3. 提取目标时间切片的二维空间数据[lat, lon]
            float[][][] data3D = (float[][][]) dataVar.read().copyToNDJavaArray();
            float[][] spatialData = data3D[targetTimeIndex]; // 单个时间切片的空间数据

            // 4. 找到目标经纬度的4个相邻空间点（矩形网格的4个顶点）
            int[] latIndices = findNearestTwoIndices(targetLat, lats); // 相邻纬度索引[i0, i1]
            int[] lonIndices = findNearestTwoIndices(targetLon, lons); // 相邻经度索引[j0, j1]

            int i0 = latIndices[0], i1 = latIndices[1];
            int j0 = lonIndices[0], j1 = lonIndices[1];

            float lat0 = lats[i0], lat1 = lats[i1]; // 相邻纬度值
            float lon0 = lons[j0], lon1 = lons[j1]; // 相邻经度值

            // 5. 收集4个邻域点，区分有效/无效（插值用到的所有空间点）
            List<SpatialPoint> usedValid = new ArrayList<>();
            List<SpatialPoint> usedInvalid = new ArrayList<>();

            // 左下点 (lat0, lon0)
            addSpatialPoint(lat0, lon0, spatialData[i0][j0], usedValid, usedInvalid);
            // 左上点 (lat0, lon1)
            addSpatialPoint(lat0, lon1, spatialData[i0][j1], usedValid, usedInvalid);
            // 右下点 (lat1, lon0)
            addSpatialPoint(lat1, lon0, spatialData[i1][j0], usedValid, usedInvalid);
            // 右上点 (lat1, lon1)
            addSpatialPoint(lat1, lon1, spatialData[i1][j1], usedValid, usedInvalid);

            // 6. 执行空间插值（根据有效点数量选择策略）
            float resultValue = Float.NaN;
            String strategy = "";
            int validCount = usedValid.size();

            if (validCount == 0) {
                strategy = "4个邻域点均无效，无法插值";
            } else if (validCount == 4) {
                // 双线性插值（4个有效点）
                float val00 = getValueByLatLon(usedValid, lat0, lon0); // 左下
                float val01 = getValueByLatLon(usedValid, lat0, lon1); // 左上
                float val10 = getValueByLatLon(usedValid, lat1, lon0); // 右下
                float val11 = getValueByLatLon(usedValid, lat1, lon1); // 右上

                // 计算权重
                float alpha = (lat1 != lat0) ? (targetLat - lat0) / (lat1 - lat0) : 0f; // 纬度权重
                float beta = (lon1 != lon0) ? (targetLon - lon0) / (lon1 - lon0) : 0f;   // 经度权重

                // 双线性插值公式
                float valLat0 = val00 * (1 - beta) + val01 * beta; // 下边缘插值
                float valLat1 = val10 * (1 - beta) + val11 * beta; // 上边缘插值
                resultValue = valLat0 * (1 - alpha) + valLat1 * alpha; // 最终插值

                strategy = "4个有效点，双线性插值";
            } else {
                // 近似插值（1-3个有效点）
                resultValue = approximateSpatialInterpolation(usedValid, validCount);
                strategy = validCount + "个有效点，近似插值（均值或单方向插值）";
            }

            // 返回插值结果
            return new InterpolationResult(
                    targetTimeIndex, targetTimeValue, targetLat, targetLon,
                    resultValue, strategy, usedValid, usedInvalid
            );

        } finally {
            if (ncFile != null) ncFile.close();
        }
    }

    // 添加空间点到有效/无效列表，并记录无效原因
    private void addSpatialPoint(float lat, float lon, float value,
                                 List<SpatialPoint> validList, List<SpatialPoint> invalidList) {
        boolean isValid = true;
        String invalidReason = "";

        if (value != value) { // NaN
            isValid = false;
            invalidReason = "NaN（非数字）";
        } else if (invalidValues.contains(value)) { // 用户定义无效值
            isValid = false;
            invalidReason = value + "（用户定义无效值）";
        } else if (value < minReasonable) { // 低于合理下限
            isValid = false;
            invalidReason = value + "（低于下限" + minReasonable + "）";
        } else if (value > maxReasonable) { // 高于合理上限
            isValid = false;
            invalidReason = value + "（高于上限" + maxReasonable + "）";
        }

        if (isValid) {
            validList.add(new SpatialPoint(lat, lon, value, true, ""));
        } else {
            invalidList.add(new SpatialPoint(lat, lon, value, false, invalidReason));
        }
    }

    // 从有效点列表中获取指定经纬度的值
    private float getValueByLatLon(List<SpatialPoint> validPoints, float lat, float lon) {
        for (SpatialPoint p : validPoints) {
            if (p.lat == lat && p.lon == lon) {
                return p.value;
            }
        }
        return Float.NaN; // 理论上不会触发（已确认有效）
    }

    // 近似空间插值（1-3个有效点）
    private float approximateSpatialInterpolation(List<SpatialPoint> validPoints, int validCount) {
        if (validCount == 1) {
            return validPoints.get(0).value; // 1个有效点：直接取值
        } else if (validCount == 2) {
            SpatialPoint p1 = validPoints.get(0);
            SpatialPoint p2 = validPoints.get(1);
            // 同纬度（lat相同）：经度方向线性插值
            if (p1.lat == p2.lat) {
                float beta = (p1.lon != p2.lon) ? (p2.lon - p1.lon) / (p2.lon - p1.lon) : 0f; // 简化权重
                return p1.value * (1 - beta) + p2.value * beta;
            }
            // 同经度（lon相同）：纬度方向线性插值
            if (p1.lon == p2.lon) {
                float alpha = (p1.lat != p2.lat) ? (p2.lat - p1.lat) / (p2.lat - p1.lat) : 0f; // 简化权重
                return p1.value * (1 - alpha) + p2.value * alpha;
            }
            // 对角点：取均值
            return (p1.value + p2.value) / 2f;
        } else { // validCount == 3
            // 3个有效点：取均值
            float sum = 0f;
            for (SpatialPoint p : validPoints) sum += p.value;
            return sum / 3f;
        }
    }

    // 查找目标值的两个相邻索引（用于获取4个邻域点）
    private int[] findNearestTwoIndices(float target, float[] array) {
        int n = array.length;
        if (n == 1) return new int[]{0, 0};

        boolean isAscending = array[n - 1] > array[0];
        int left = 0, right = n - 1;

        while (left < right - 1) {
            int mid = (left + right) / 2;
            if ((isAscending && array[mid] < target) || (!isAscending && array[mid] > target)) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return new int[]{left, right};
    }

    // 打印结果（仅三类信息）
    public void printResult(InterpolationResult result) {
        System.out.println("==================================== 插值汇总 ====================================");
        System.out.printf("目标时间索引：%d（时间值：%.2f）%n", result.targetTimeIndex, result.targetTimeValue);
        System.out.printf("目标经纬度：(%.4f°N, %.4f°E)%n", result.targetLat, result.targetLon);
        System.out.println("插值策略：" + result.strategy);
        if (!Float.isNaN(result.interpolatedValue)) {
            System.out.println("空间插值结果：" + dataFormat.format(result.interpolatedValue));
        } else {
            System.out.println("空间插值结果：无法计算");
        }

        System.out.println("\n==================================== 插值用到的有效空间点 ====================================");
        if (result.usedValidPoints.isEmpty()) {
            System.out.println("无有效空间点");
        } else {
            for (SpatialPoint p : result.usedValidPoints) {
                System.out.printf("经纬度：(%.4f°N, %.4f°E) → 数值：%s%n",
                        p.lat, p.lon, dataFormat.format(p.value));
            }
        }

        System.out.println("\n==================================== 插值用到的无效空间点 ====================================");
        if (result.usedInvalidPoints.isEmpty()) {
            System.out.println("无无效空间点");
        } else {
            for (SpatialPoint p : result.usedInvalidPoints) {
                System.out.printf("经纬度：(%.4f°N, %.4f°E) → 数值：%s → 无效原因：%s%n",
                        p.lat, p.lon, dataFormat.format(p.value), p.invalidReason);
            }
        }
        System.out.println("======================================================================================");
    }

    // 测试主方法
    public static void main(String[] args) {
        try {
            // 1. 配置参数（根据数据类型调整）
            List<Float> invalidValues = List.of(-9999f, -999f); // 无效值列表
            float minReasonable = -100f; // 合理下限（如海洋温度）
            float maxReasonable = 100f; // 合理上限（如海洋温度）
            SingleTimeGridInterpolator4 interpolator = new SingleTimeGridInterpolator4(
                    invalidValues, minReasonable, maxReasonable, "0.00"
            );

            // 2. 目标参数（单个时间网格的空间插值）
            String ncPath = "E:\\NMF_BEN_GO_GLDT_2025090900_168h_SST_cropped.nc";
            String dataVarName = "sst"; // 数据变量名
            int targetTimeIndex = 0; // 目标时间索引（选择第0个时间切片）
            float targetLat = 40.3f; // 目标纬度
            float targetLon = 122.1f; // 目标经度

            // 3. 执行插值并打印结果
            InterpolationResult result = interpolator.interpolateInSingleTimeGrid(
                    ncPath, dataVarName, targetTimeIndex, targetLat, targetLon
            );
            interpolator.printResult(result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}